/*
 * Copyright 2003-2006 (c) Live Media Pty Ltd. <argot@einet.com.au> 
 *
 * This software is licensed under the Argot Public License 
 * which may be found in the file LICENSE distributed 
 * with this software.
 *
 * More information about this license can be found at
 * http://www.einet.com.au/License
 * 
 * The Developer of this software is Live Media Pty Ltd,
 * PO Box 4591, Melbourne 3001, Australia.  The license is subject 
 * to the law of Victoria, Australia, and subject to exclusive 
 * jurisdiction of the Victorian courts.
 */

%module(package = "argot") type

%include "utils.i"

%define %argot_library_callback_code_forward_decl
%{
	int _library_reader_callback( type_input_stream * in, type_element * element, void* data );
	int _library_writer_callback( type_output_stream * out, void * object, type_element * element, void* data );
	int _lua_type_library_bind( type_library * library, type_name * name, SWIGLUA_FN reader, SWIGLUA_FN writer );
	int _lua_type_library_register_bind( type_library * library, type_name * name, type_element * structure, SWIGLUA_FN reader, SWIGLUA_FN writer );
	void _lua_type_library_destroy( type_library * library );
%}
%enddef

%define %argot_library_callback_code
	/*%constant int _library_reader_callback( type_input_stream * in, type_element * element, void* data );
	%constant int _library_writer_callback( type_output_stream * out, void * object, type_element * element, void* data );*/
	
	int _lua_type_library_bind( type_library * library, type_name * name, SWIGLUA_FN reader, SWIGLUA_FN writer );
	int _lua_type_library_register_bind( type_library * library, type_name * name, type_element * structure, SWIGLUA_FN reader, SWIGLUA_FN writer );
	void _lua_type_library_destroy( type_library * library );


	%{
		void _lua_type_library_decref_reader_writer( type_library * library, int id )
		{			
			type_reader* reader = type_library_get_reader( library, id );
			type_writer* writer = type_library_get_writer( library, id );
			
			TRACE("decreffing reader/writer");

			if (reader) 
			{
				freeLuaFunction((luaFunctionHandle)reader->data);
			}
			
			if (writer)
			{
				freeLuaFunction((luaFunctionHandle)writer->data);
			}
		}

		void _lua_type_library_destroy( type_library * library )
		{
			// loop over all definitions in the library and xdecref their readers & writers. otherwise we get mem leaks.
			int i;
			for (i = 0; i < library->count; i++)
			{
				_lua_type_library_decref_reader_writer( library, i );
			}
			// todo: -- core dump --
			//type_library_destroy(library);
		}


		void _lua_type_library_set_reader_and_writer_data( type_library * library, type_name * name, SWIGLUA_FN reader, luaFunctionHandle* readerData, SWIGLUA_FN writer, luaFunctionHandle* writerData )
		{
			int id = type_library_get_id( library, name );
			
			if (id != TYPE_INVALID_PARAM)
			{
				_lua_type_library_decref_reader_writer( library, id );
			}

			*readerData = storeLuaFunction(reader);
			*writerData = storeLuaFunction(writer);
		}

		
		int _lua_type_library_bind( type_library * library, type_name * name, SWIGLUA_FN reader, SWIGLUA_FN writer )
		{
			luaFunctionHandle readerData, writerData;
			_lua_type_library_set_reader_and_writer_data(library, name, reader, &readerData, writer, &writerData);
			
			return type_library_bind( library, name, _library_reader_callback, readerData, _library_writer_callback, writerData );
		}
		
		int _lua_type_library_register_bind( type_library * library, type_name * name, type_element * structure, SWIGLUA_FN reader, SWIGLUA_FN writer )
		{
			luaFunctionHandle readerData, writerData;
			_lua_type_library_set_reader_and_writer_data(library, name, reader, &readerData, writer, &writerData);

			return type_library_register_bind( library, name, structure, _library_reader_callback, readerData, _library_writer_callback, writerData );
		}


		int _library_reader_callback( type_input_stream * in, type_element * element, void* data )
		{
			int result;
			int res = 0;
			luaFunctionHandle func = (luaFunctionHandle)data;
					
			lua_State* state = getLuaFunctionState(func);
			printf("stacksize %d\n", lua_gettop(state));
			
			/*callable = _validate_callable(data);
			if (!callable) return 0;*/

			pushLuaFunction(func);
			SWIG_NewPointerObj( state, in, SWIGTYPE_p_type_input_stream, 0 );
			SWIG_NewPointerObj( state, element, SWIGTYPE_p_type_element, 0 );
			
			
			//res = lua_pcall( state, 2, 1, 0 );
			lua_call( state, 2, 1 );

			result = 0;

			/*if (res != 0)
			{
				print_traceback(state, "Error in library reader callback!\n");
			}
			else
			{*/
				if (!lua_isnumber(state, -1))
				{
					printf("You have to return an int in your lua reader callback function!\n");
				}
				else
				{
					result = (int)( lua_tonumber(state, -1) );
				}
				lua_pop(state, 1);	// pop result
			//}
			
			return result;			
		}
		
		int _library_writer_callback( type_output_stream * out, void * object, type_element * element, void* data )
		{
			int result = TYPE_OK;
			int res = 0;
			luaFunctionHandle func = (luaFunctionHandle)data;
			
			lua_State* state = getLuaFunctionState(func);
			SWIGLUA_OBJ* lua_object = (SWIGLUA_OBJ*)object;
			
			/*callable = _validate_callable(data);
			if (!callable) return 0;*/

			pushLuaFunction(func);
			SWIG_NewPointerObj( state, out, SWIGTYPE_p_type_output_stream, 0 );
			
			lua_pushvalue(lua_object->L, lua_object->idx);
			lua_xmove(lua_object->L, state, 1);
			
			SWIG_NewPointerObj( state, element, SWIGTYPE_p_type_element, 0 );
			
			//res = lua_pcall( state, 3, 0, 0 );
			lua_call( state, 3, 0 );

			return result;		
		}
		
		/*void _lua_library_get_reader( type_library* library, int id )
		{
			if (type_library_get_reader(library) == _library_reader_callback)
			{
				lua_push
			}
		}*/
	%}

	%add_bound_method( type_library, int, Bind, _lua_type_library_bind, type_name * name, SWIGLUA_FN reader, SWIGLUA_FN writer );
	%add_bound_method( type_library, int, Register_bind, _lua_type_library_register_bind, type_name * name, type_element * structure, SWIGLUA_FN reader, SWIGLUA_FN writer );

/*
	%pythoncode
	%{
	def _library_get_reader(self, id):
		result = type_library_get_reader( self, id )
		if result and (result.reader == _library_reader_callback):
			return py_convert_to_python_object( result.data )
		return result

	TypeLibrary.get_reader = _library_get_reader


	def _library_get_writer(self, id):
		result = type_library_get_writer( self, id )
		if result and (result.writer == _library_writer_callback):
			return py_convert_to_python_object( result.data )
		return result

	TypeLibrary.get_writer = _library_get_writer
	%}
*/

%enddef

%define %type_library_destructor
	%set_destructor( type_library, _lua_type_library_destroy );
%enddef



%define %type_reader_auto_callback_code

	%{
		typedef struct TypeReaderAuto
		{
			luaFunctionHandle func;
		} TypeReaderAuto;
		
		void* lua_type_reader_auto_callback( type_list * list, type_map * map, void * data );
	%}


	typedef struct TypeReaderAuto
	{
	} TypeReaderAuto;


	%extend TypeReaderAuto
	{
		TypeReaderAuto(SWIGLUA_FN constructor)
		{
			TypeReaderAuto* self = calloc(1, sizeof(TypeReaderAuto));
			
			self->func = storeLuaFunction(constructor);
			
			return self;
		}
		
		~TypeReaderAuto()
		{
			freeLuaFunction(self->func);
		}
		
		int read( type_input_stream * in, type_element * element )
		{
			return type_reader_auto( in, element, lua_type_reader_auto_callback, self );
		}
	}
 
	%inline
	%{
		void* lua_type_reader_auto_callback( type_list * list, type_map * map, void* data )
		{
			TypeReaderAuto* reader = (TypeReaderAuto*)data;
			lua_State* state = getLuaFunctionState(reader->func);
			SWIGLUA_FN obj;
			luaFunctionHandle obj2;

			pushLuaFunction(reader->func);

			/*callable = _validate_callable(data);
			if (!callable) return 0;*/

			SWIG_NewPointerObj( state, list, SWIGTYPE_p_type_list, 0 );
			SWIG_NewPointerObj( state, map, SWIGTYPE_p_type_map, 0 );
			
			lua_call(state, 2, 1);

			//if ( !res ) print_traceback();

			// we actually return the current object here, not a func. It works nonetheless.
			obj.L = state;
			obj.idx = lua_gettop(state);
			obj2 = storeLuaFunction(obj);
			lua_pop(state, 1);
		
			return obj2;
		}
	%}

	%typemap(out) SWIGLUA_OBJ*
	%{  
		SWIG_arg++;
	%}


	%inline
	%{
		SWIGLUA_OBJ* convert_to_lua_object(void* obj)
		{
			// pushes the object actually
			pushLuaFunction( (luaFunctionHandle)obj );
			return 0;
		}
	%}
%enddef



%define %insert_read_callback

	%constant int lua_argot_read_callback( type_input_stream * in, void * data, char * buffer, int size );

	%{
		#ifndef LUA_ARGOT_READ_CALLBACK_DEFINED
		#define LUA_ARGOT_READ_CALLBACK_DEFINED
		
		int lua_argot_read_callback( type_input_stream * in, void * data, char * buffer, int size )
		{
			int length;
			luaFunctionHandle func = (luaFunctionHandle)data;
			lua_State* state = getLuaFunctionState(func);

			/*callable = _validate_callable(data);
			if (!callable) return 0;*/
			
			pushLuaFunction(func);
			lua_pushnumber(state, size);
			lua_call( state, 1, 1 );

			//if ( !res ) print_traceback();
			
			if ( !lua_isstring(state, -1) )
			{
				printf("You have to return a string in your lua argot_read callback function!\n");
				length = 0;
			}
			else
			{
				const char* luastring = lua_tostring (state, -1);
				length = lua_strlen(state, -1);
    				
				assert( size == length );
				memcpy( buffer, luastring, sizeof(char) * length );
			}
			
			lua_pop(state, 1);
			
			return length;
		}
		
		#endif
	%}

%enddef



%define %to_string_to_void_code
	%inline
	%{
		char* toString(void* ptr)
		{
			return (char*)ptr;
		}
		
		/*void* toVoid(PyObject* ptr)
		{
			return ptr;
		}*/
	%}
	
	%extend type_stack
	{
		void set_object(type_element* element, SWIGLUA_OBJ obj)
		{
			SWIGLUA_FN tmpFn;
			luaFunctionHandle func;
			
			tmpFn.L = obj.L;
			tmpFn.idx = obj.idx;
				
			func = storeLuaFunction(tmpFn);
			type_stack_set_ptr(self, element, func);
		}
	}
%enddef


%define %stream_init_code
	%inline
	%{
		void lua_argot_input_stream_init( type_input_stream * in, type_map * map, SWIGLUA_FN reader )
		{
			luaFunctionHandle func = storeLuaFunction(reader);
			
			type_input_init( in, map, lua_argot_read_callback, func );
		}
	%}
%enddef




%define %insert_write_callback
	//%constant int lua_argot_write_callback( type_output_stream * out, void * data, char * buffer, int size );

	%{
		#ifndef LUA_ARGOT_WRITE_CALLBACK_DEFINED
		#define LUA_ARGOT_WRITE_CALLBACK_DEFINED
		
		int lua_argot_write_callback( type_output_stream * out, void * data, char * buffer, int size )
		{
			int result = TYPE_OK;
			luaFunctionHandle func = (luaFunctionHandle)data;
			lua_State* state = getLuaFunctionState(func);
			

			pushLuaFunction(func);			
			/*callable = _validate_callable(data);
			if (!callable) return 0;*/
			
			lua_pushlstring(state, buffer, size);
			lua_call( state, 1, 0 );

			//if ( !res ) print_traceback();
			
			return result;
		}
		
		#endif
	%}

%enddef


%{
	typedef struct{
	  lua_State* L; /* the state */
	  int idx;      /* the index on the stack */
	} SWIGLUA_OBJ;

	#define SWIGLUA_OBJECT_GET(obj) {lua_pushvalue(obj.L,obj.idx);}
%}

// the actual typemap
%typemap(in) SWIGLUA_OBJ
%{  
	$1.L=L; $1.idx=$input; 
%}


%define %output_stream_forward_decls
%{
	void lua_argot_output_stream_init( type_output_stream * out, type_map * map, SWIGLUA_FN writer );
	int lua_argot_output_stream_write_named_object( type_output_stream * out, type_name* name, SWIGLUA_OBJ obj  );
	int lua_argot_output_stream_write_object( type_output_stream * out, int id, SWIGLUA_OBJ obj );
%}
%enddef

%define %output_stream_code
	%{
		#include "common/BigEndianSignedByte.h"
		#include "common/BigEndianSignedInteger.h"
		#include "common/BigEndianSignedLong.h"
		#include "common/BigEndianSignedShort.h"
		#include "common/BigEndianUnsignedByte.h"
		#include "common/BigEndianUnsignedInteger.h"
		#include "common/BigEndianUnsignedLong.h"
		#include "common/BigEndianUnsignedShort.h"
		#include "common/U8Ascii.h"
		#include "common/U8Boolean.h"
		#include "common/U16ArrayByte.h"
		#include "common/U32ArrayByte.h"
		#include "common/U32UTF8.h"

		void* getFinalObj(type_name* typename, SWIGLUA_OBJ* obj, void* memory)
		{
			// todo: error checking
			lua_State* state = obj->L;
			int idx = obj->idx;
			
			char* name = typename->name;
			
			if ( !strcmp(name, uint8_name.name) )
			{
				*(uint8_t*)memory = (uint8_t)lua_tonumber(state, idx);
			}
			else if ( !strcmp(name, sint8_name.name) )
			{
				*(int8_t*)memory = (int8_t)lua_tonumber(state, idx);
			}
			else if ( !strcmp(name, uint16_name.name) )
			{
				*(uint16_t*)memory = (uint16_t)lua_tonumber(state, idx);
			}
			else if ( !strcmp(name, sint16_name.name) )
			{
				*(int16_t*)memory = (int16_t)lua_tonumber(state, idx);
			}
			else if ( !strcmp(name, uint32_name.name) )
			{
				*(uint32_t*)memory = (uint32_t)lua_tonumber(state, idx);
			}
			else if ( !strcmp(name, sint32_name.name) )
			{
				*(int32_t*)memory = (int32_t)lua_tonumber(state, idx);
			}
			else if ( !strcmp(name, uint64_name.name) )
			{
				*(uint64_t*)memory = (uint64_t)lua_tonumber(state, idx);
			}
			else if ( !strcmp(name, sint64_name.name) )
			{
				*(int64_t*)memory = (int64_t)lua_tonumber(state, idx);
			}
			else if ( !strcmp(name, bool_name.name) )
			{
				*(char*)memory = (lua_toboolean(state, idx)) ? 0 : 1;
			}
			else if ( !strcmp(name, u8ascii_name.name) )
			{
				const char* buffer = 0;
				int length = 0;
				
				length = lua_strlen( state, idx );
				buffer = lua_tostring( state, idx );

				memcpy(memory, buffer, (length + 1) * sizeof(char));
			}
			else if ( !strcmp(name, u32utf8_name.name) )
			{
				printf("u32utf8 not yet implemented!");
			}
			else if ( !strcmp(name, u16binary_name.name) )
			{
				printf("u16binary not yet implemented!");
			}
			else if ( !strcmp(name, u32binary_name.name) )
			{
				printf("u32binary not yet implemented!");
			}
			else
			{
				return obj;
			}

			return memory;
		}
	%}


	%inline
	%{
		void lua_argot_output_stream_init( type_output_stream * out, type_map * map, SWIGLUA_FN writer )
		{
			luaFunctionHandle func = storeLuaFunction(writer);
			
			type_output_init( out, map, lua_argot_write_callback, func );
		}

		int lua_argot_output_stream_write_named_object( type_output_stream * out, type_name* name, SWIGLUA_OBJ obj  )
		{
			// todo: make non-fixed size
			static char memory[65536];
			return type_output_write_named_object( out, name, getFinalObj(name, &obj, memory) );
		}

		int lua_argot_output_stream_write_object( type_output_stream * out, int id, SWIGLUA_OBJ obj )
		{
			return lua_argot_output_stream_write_named_object( out, type_map_get_name( out->map, id ), obj );
		}

	%}

	%set_constructor_2( type_output_stream, lua_argot_output_stream_init, type_map*, map, SWIGLUA_FN, writer);
	%add_bound_method( type_output_stream, int, write_object, lua_argot_output_stream_write_object, int systemId, SWIGLUA_OBJ object );
	%add_bound_method( type_output_stream, int, write_named_object, lua_argot_output_stream_write_named_object, type_name * name, SWIGLUA_OBJ object );
	
%enddef


%define %input_stream_constructor
	%{
		void lua_argot_input_stream_init( type_input_stream * in, type_map * map, SWIGLUA_FN reader );
	%}
%enddef

%define %set_input_stream_constructor
	%set_constructor_2(type_input_stream, lua_argot_input_stream_init, type_map *, map, SWIGLUA_FN, reader );
%enddef


%define %type_element_forward_decl
%{
bool _register_element_bind_func( type_element * element, SWIGLUA_FN fn );
void _unregister_element_bind_func( type_element * element );
int _element_bind_callback( type_element * element, type_definition * definition );
void _type_element_constructor(type_element* e, SWIGLUA_FN fn);
void _type_element_destructor(type_element* e);
%}
%enddef




%define %type_element_bind_callbacks

%set_constructor_1( type_element, _type_element_constructor, SWIGLUA_FN, fn );
%set_destructor( type_element, _type_element_destructor);

//bool _register_element_bind_func( type_element * element, lua_State* callable );
//void _unregister_element_bind_func( type_element * element );
%constant int _element_bind_callback( type_element * element, type_definition * definition );
//void _type_element_constructor(type_element* e, lua_State* bindFunc);
//void _type_element_destructor(type_element* e);

%{
	void _type_element_constructor(type_element* e, SWIGLUA_FN fn)
	{
		_register_element_bind_func(e, fn);	
		type_element_init(e, _element_bind_callback);
	}

	void _type_element_destructor(type_element* e)
	{
		_unregister_element_bind_func(e);
	}

    bool _register_element_bind_func( type_element * element, SWIGLUA_FN fn )
    {
		if ( !_validate_callable(fn.L, -1) )
		{
			element->user_data = 0;
			return false;
		}
		
		element->user_data = storeLuaFunction(fn);;
		
		return true;
    }
    
    void _unregister_element_bind_func( type_element * element )
    {
		luaFunctionHandle func = (luaFunctionHandle)(element->user_data);
		freeLuaFunction(func);
    }


	int _element_bind_callback( type_element * element, type_definition * definition )
	{
		int result;
		int res = 0;
		
		luaFunctionHandle func = (luaFunctionHandle)(element->user_data);
		lua_State* state = getLuaFunctionState(func);
		
		/*if ()
		{
			printf("No lua bind function associated with this element!\n");
			return TYPE_BIND_ERROR;
		}*/
		

		pushLuaFunction(func);
		SWIG_NewPointerObj( state, element, SWIGTYPE_p_type_element, 0 );
		SWIG_NewPointerObj( state, definition, SWIGTYPE_p_type_definition, 0 );
		
		//res = lua_pcall( state, 2, 1, 0 );
		lua_call( state, 2, 1 );

		result = TYPE_BIND_ERROR;

        if (res != 0)
        {
			//print_traceback(state, "Error in bind callback!\n");
		}
		else
		{			
			if (!lua_isnumber(state, -1))
			{
				printf("You have to return an int in your element's bind function!\n");
			}
			else
			{
				result = (int)( lua_tonumber(state, -1) );
			}
			lua_pop(state, 1);	// pop result
		}
		
		return result;
	}
%}

%enddef



%include "../../swig/type.i"

